﻿using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using Aquarinum.WebSocekt.Client.Core;
using Aquarinum.WebSocekt.Client.Core.EventArgs;
using Aquarinum.WebSocekt.Client.Core.Interfaces;
using Aquarinum.WebSocekt.Core.Dtos;
using Aquarinum.WebSocekt.Core.Utils;
using Newtonsoft.Json;
using SuperSocket.ClientEngine;
using WebSocket4Net;

namespace Aquarinum.WebSocekt.Client
{
    public class DefaultWebsocketClient : IWebsocketClient
    {
        private WebSocket _client;
        private bool _hasInit;
        private DateTime _lastHeartbrat = DateTime.Now;

        public void Dispose()
        {
            _client.Close("client has been Gc");
            _client.Dispose();
        }

        /// <inheritdoc />
        public string Host { get; private set; }

        /// <inheritdoc />
        public string User { get; private set; }

        /// <inheritdoc />
        public string Resource { get; private set; }

        /// <inheritdoc />
        public string Clientid { get; private set; }

        /// <inheritdoc />
        public short WebSocketState
        {
            get
            {
                if (_client?.State != null) return (short) _client?.State;
                return -1;
            }
        }

        public void InitClient(string user, string resource, string host)
        {
            if (!_hasInit)
            {
                Host = host;
                User = user;
                Resource = resource;
                Clientid = $"{user};{resource}";
                _hasInit = true;
            }
            else
            {
                throw new ArgumentException("client only can be inited once");
            }
        }

        public void ConnectServer()
        {
            var handShake = new HandShakeDto {Domain = Host, Resource = Resource, User = User};
            var postData = JsonConvert.SerializeObject(handShake);
            var authurl = $"http://{Host}/Api/Auth/CreateToken";
            string token;
            using (var http = new HttpClient())
            {
                http.Timeout = new TimeSpan(0, 0, 0, 30);
                var httpContent = new StringContent(postData);
                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var result = http.PostAsync(authurl, httpContent).Result;
                var deresponsejson = result.Content.ReadAsStringAsync().Result;
                token = JsonConvert.DeserializeObject<AuthResultDto>(deresponsejson)?.Token;
            }

            if (string.IsNullOrEmpty(token)) OnClientUnAuth?.Invoke(this, null);
            _client = new WebSocket($"ws://{Host}/ws?token={token}");
            _client.Opened += (sender, args) => { OnClientOpened?.Invoke(sender, args); };
            _client.Closed += (sender, args) => { OnClientClosed?.Invoke(sender, args); };
            _client.MessageReceived += (sender, args) =>
            {
                MessageBase messagebase = null;
                try
                {
                    messagebase = JsonConvert.DeserializeObject<MessageBase>(args.Message);
                }
                catch (Exception e)
                {
                    // ignored
                }

                if (messagebase != null)
                    switch (messagebase.Type)
                    {
                        case "CLINET_CONNECTED": //客户端连接成功
                            OnClientConnected?.Invoke(this,
                                new WebSoketMessageReciveArgs
                                {
                                    Message = JsonConvert.DeserializeObject<WebsocketMessageDto>(args.Message)
                                });
                            _lastHeartbrat = DateTime.Now;
                            BeginHeartbrat(); //开始心跳
                            break;
                        case "HEARTBEAT_SUCCESS": //刷新上次心跳时间
                            _lastHeartbrat = DateTime.Now;
                            break;
                        case "MESSAGE_RECEIPT": //处理消息回执
                            OnMessageReceipt?.Invoke(this, new WebSoketMessageReciveArgs
                            {
                                Message = JsonConvert.DeserializeObject<WebsocketMessageDto>(args.Message)
                            });
                            break;
                        case "CLINET_UNAUTH": //处理授权失败
                            OnClientUnAuth?.Invoke(this,
                                new WebSoketMessageReciveArgs
                                {
                                    Message = JsonConvert.DeserializeObject<WebsocketMessageDto>(args.Message)
                                });
                            break;
                        case "CLINET_HEARTBEAT": //不处理
                            break;
                        case "MESSAGE_RESEND": //不处理
                            break;
                        default:
                            var message = JsonConvert.DeserializeObject<WebsocketMessageDto>(args.Message);
                            if (message != null)
                            {
                                OnRecivedMessage?.Invoke(this,
                                    new WebSoketMessageReciveArgs
                                    {
                                        Message = message
                                    });

                                if (message.NeedReceipt) //如果消息需要回执
                                    SendReceipt(message);
                            }

                            break;
                    }
            };
            _client.Error += (sender, args) => { OnError?.Invoke(sender, args); };
            _client.Open();
        }

        public ApiResult SendMessage<T>(T message, string messageTo) where T : class
        {
            var messageTypeAttribute = (MessageTypeAttribute)
                typeof(T).GetCustomAttributes(typeof(MessageTypeAttribute), false).FirstOrDefault();
            var messageTypeName = messageTypeAttribute != null ? messageTypeAttribute.Name : typeof(T).Name;
            return SendMessage(message, messageTypeName, messageTo);
        }

        public ApiResult SendMessage<T>(T message, string messageType, string messageTo) where T : class
        {
            var wsmessage = new WebsocketMessageDto
            {
                Body = JsonConvert.SerializeObject(message),
                Type = messageType,
                To = messageTo,
                Domain = Host,
                From = Clientid,
                Id = Guid.NewGuid().ToString(),
                Timestamp = DateTimeHelper.GetUnixTimestamp()
            };
            return SendMessage(wsmessage);
        }

        public void Close()
        {
            _client.Close("未知原因");
        }

        public void Close(string reason)
        {
            _client.Close(reason);
        }

        public event EventHandler OnClientClosed;
        public event EventHandler OnClientOpened;
        public event EventHandler<WebSoketMessageReciveArgs> OnMessageReceipt;
        public event EventHandler<WebSoketMessageReciveArgs> OnClientConnected;
        public event EventHandler<WebSoketMessageReciveArgs> OnClientUnAuth;
        public event EventHandler<WebSoketMessageReciveArgs> OnRecivedMessage;

        /// <summary>
        ///     发送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public ApiResult SendMessage(WebsocketMessageDto message)
        {
            var postData = JsonConvert.SerializeObject(message);
            var url = $"http://{Host}/Api/WebSocket/Message/Send";

            ApiResult apiResult;
            using (var http = new HttpClient())
            {
                http.Timeout = new TimeSpan(0, 0, 0, 30);
                var httpContent = new StringContent(postData);
                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var result = http.PostAsync(url, httpContent).Result;
                var deresponsejson = result.Content.ReadAsStringAsync().Result;
                apiResult = JsonConvert.DeserializeObject<ApiResult>(deresponsejson);
            }


            return apiResult;
        }

        public event EventHandler<ErrorEventArgs> OnError;

        private void BeginHeartbrat()
        {
            var heartbeatthread = new Thread(() =>
            {
                while (true)
                {
                    Thread.Sleep(1000);
                    var heartbrat = new ClinetHeartbeatDto
                    {
                        Type = ConstSocketMessageType.CLINET_HEARTBEAT,
                        Domain = Host,
                        Lasttimestamp = DateTimeHelper.GetUnixTimestamp(),
                        Resource = Resource,
                        User = User
                    };
                    _client.Send(JsonConvert.SerializeObject(heartbrat));
                }
            }) {IsBackground = true};

            var retryCount = 0;

            var checkHeartbeatthread = new Thread(() =>
                {
                    while (true)
                    {
                        Thread.Sleep(1000 * 2);
                        if ((DateTime.Now - _lastHeartbrat).Seconds > 60) //如果最后心跳时间已经超过1分钟没更新，关闭客户端，并且出发客户端掉线事件
                            if (retryCount > 5) //每5秒重试一次，共重试5次
                            {
                                OnClientClosed?.Invoke(this, null);
                                _client.Close("心跳超时");
                                _client.Dispose();
                            }
                            else
                            {
                                _client.Close();
                                _client.Dispose();
                                ConnectServer();
                                Thread.Sleep(1000 * 5);
                            }
                    }
                })
                {IsBackground = true};

            heartbeatthread.Start();
            checkHeartbeatthread.Start();
        }

        /// <summary>
        ///     发送回执
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private ApiResult SendReceipt(WebsocketMessageDto message)
        {
            var receiptdto = new ReceiptMessageDto {SourceMessage = message};
            var postData = JsonConvert.SerializeObject(receiptdto);


            var url = $"http://{Host}/Api/WebSocket/Message/Receipt";

            ApiResult apiResult;
            using (var http = new HttpClient())
            {
                http.Timeout = new TimeSpan(0, 0, 0, 30);
                var httpContent = new StringContent(postData);
                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var result = http.PostAsync(url, httpContent).Result;
                var deresponsejson = result.Content.ReadAsStringAsync().Result;
                apiResult = JsonConvert.DeserializeObject<ApiResult>(deresponsejson);
            }

            return apiResult;
        }
    }
}